package health_system.health_system.service.impl;

import health_system.health_system.dto.*;
import health_system.health_system.entity.Moment;
import health_system.health_system.entity.MomentShare;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.MomentNotFoundException;
import health_system.health_system.exception.MomentShareNotFoundException;
import health_system.health_system.exception.ResourceNotFoundException;
import health_system.health_system.repository.MomentRepository;
import health_system.health_system.repository.MomentShareRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.MomentShareService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class MomentShareServiceImpl implements MomentShareService {

    private final MomentShareRepository momentShareRepository;
    private final MomentRepository momentRepository;
    private final UserRepository userRepository;
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentShares", "userShares", "shareCount", "momentShareStatus"}, allEntries = true)
    })
    public MomentShareDTO shareMoment(MomentShareCreateDTO shareCreateDTO) {
        log.debug("Sharing moment ID: {} by user ID: {}", 
                shareCreateDTO.getOriginalMomentId(), shareCreateDTO.getUserId());
        
        User user = userRepository.findById(shareCreateDTO.getUserId())
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", shareCreateDTO.getUserId()));
        
        Moment originalMoment = momentRepository.findById(shareCreateDTO.getOriginalMomentId())
                .orElseThrow(() -> new MomentNotFoundException(shareCreateDTO.getOriginalMomentId()));
        
        // Check if user has already shared this moment
        if (momentShareRepository.existsByUserAndOriginalMoment(user, originalMoment)) {
            throw new BusinessException("User has already shared this moment");
        }
        
        MomentShare share = new MomentShare();
        share.setUser(user);
        share.setOriginalMoment(originalMoment);
        share.setAdditionalComment(shareCreateDTO.getAdditionalComment());
        
        // Set shared as moment if provided
        if (shareCreateDTO.getSharedAsMomentId() != null) {
            Moment sharedAsMoment = momentRepository.findById(shareCreateDTO.getSharedAsMomentId())
                    .orElseThrow(() -> new MomentNotFoundException(shareCreateDTO.getSharedAsMomentId()));
            share.setSharedAsMoment(sharedAsMoment);
        }
        
        try {
            MomentShare savedShare = momentShareRepository.save(share);
            log.info("Successfully shared moment ID: {} by user ID: {} with share ID: {}", 
                    originalMoment.getId(), user.getId(), savedShare.getId());
            return MomentShareDTO.fromEntity(savedShare);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to create share due to data integrity violation", e);
            throw new BusinessException("Failed to share moment. The share might already exist.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentShares", "userShares", "shareCount", "momentShareStatus"}, allEntries = true)
    })
    public MomentShareDTO updateShare(Long id, MomentShareUpdateDTO updateDTO) {
        log.debug("Updating share with ID: {}", id);
        
        MomentShare share = getShareEntityById(id);
        
        if (updateDTO.getAdditionalComment() != null) {
            share.setAdditionalComment(updateDTO.getAdditionalComment());
        }
        
        if (updateDTO.getSharedAsMomentId() != null) {
            Moment sharedAsMoment = momentRepository.findById(updateDTO.getSharedAsMomentId())
                    .orElseThrow(() -> new MomentNotFoundException(updateDTO.getSharedAsMomentId()));
            share.setSharedAsMoment(sharedAsMoment);
        }
        
        try {
            MomentShare updatedShare = momentShareRepository.save(share);
            log.info("Successfully updated share with ID: {}", updatedShare.getId());
            return MomentShareDTO.fromEntity(updatedShare);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to update share with ID: {} due to data integrity violation", id, e);
            throw new BusinessException("Failed to update share. Check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentShares", "userShares", "shareCount", "momentShareStatus"}, allEntries = true)
    })
    public void deleteShare(Long id) {
        log.debug("Deleting share with ID: {}", id);
        
        if (!momentShareRepository.existsById(id)) {
            throw new MomentShareNotFoundException(id);
        }
        
        try {
            momentShareRepository.deleteById(id);
            log.info("Successfully deleted share with ID: {}", id);
        } catch (Exception e) {
            log.error("Failed to delete share with ID: {}", id, e);
            throw new BusinessException("Failed to delete share with ID: " + id, e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {"momentShares", "userShares", "shareCount", "momentShareStatus"}, allEntries = true)
    })
    public void unshareMoment(Long userId, Long originalMomentId) {
        log.debug("Unsharing moment ID: {} by user ID: {}", originalMomentId, userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        // Check if share exists
        if (!momentShareRepository.existsByUserAndOriginalMoment(user, originalMoment)) {
            throw new BusinessException("Share does not exist for this user and moment");
        }
        
        try {
            momentShareRepository.deleteByUserAndOriginalMoment(user, originalMoment);
            log.info("Successfully unshared moment ID: {} by user ID: {}", originalMomentId, userId);
        } catch (Exception e) {
            log.error("Failed to unshare moment ID: {} by user ID: {}", originalMomentId, userId, e);
            throw new BusinessException("Failed to unshare moment", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userSharedMoment", key = "#userId + '-' + #originalMomentId")
    public boolean hasUserSharedMoment(Long userId, Long originalMomentId) {
        log.debug("Checking if user ID: {} has shared moment ID: {}", userId, originalMomentId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        return momentShareRepository.existsByUserAndOriginalMoment(user, originalMoment);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentShare", key = "#id")
    public MomentShareDTO getShareById(Long id) {
        log.debug("Getting share by ID: {}", id);
        
        MomentShare share = getShareEntityById(id);
        return MomentShareDTO.fromEntity(share);
    }

    @Override
    @Transactional(readOnly = true)
    public MomentShare getShareEntityById(Long id) {
        log.debug("Getting share entity by ID: {}", id);
        
        return momentShareRepository.findById(id)
                .orElseThrow(() -> new MomentShareNotFoundException(id));
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<MomentShare> findShareEntityById(Long id) {
        log.debug("Finding share entity by ID: {}", id);
        
        return momentShareRepository.findById(id);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMomentShare", key = "#userId + '-' + #originalMomentId")
    public Optional<MomentShareDTO> getShareByUserAndOriginalMoment(Long userId, Long originalMomentId) {
        log.debug("Getting share by user ID: {} and original moment ID: {}", userId, originalMomentId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        return momentShareRepository.findByUserAndOriginalMoment(user, originalMoment)
                .map(MomentShareDTO::fromEntity);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentShares", key = "#originalMomentId")
    public List<MomentShareDTO> getSharesByOriginalMomentId(Long originalMomentId) {
        log.debug("Getting all shares for original moment ID: {}", originalMomentId);
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        List<MomentShare> shares = momentShareRepository.findByOriginalMomentOrderByCreatedAtDesc(originalMoment);
        
        if (shares.isEmpty()) {
            return Collections.emptyList();
        }
        
        return shares.stream()
                .map(MomentShareDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentSharesPaged", 
              key = "#originalMomentId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentShareDTO> getSharesByOriginalMomentId(Long originalMomentId, Pageable pageable) {
        log.debug("Getting paginated shares for original moment ID: {} with page: {}", 
                originalMomentId, pageable.getPageNumber());
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        Page<MomentShare> sharesPage = momentShareRepository.findByOriginalMomentOrderByCreatedAtDesc(originalMoment, pageable);
        
        Page<MomentShareDTO> dtoPage = sharesPage.map(MomentShareDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userShares", key = "#userId")
    public List<MomentShareDTO> getSharesByUserId(Long userId) {
        log.debug("Getting all shares by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentShare> shares = momentShareRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (shares.isEmpty()) {
            return Collections.emptyList();
        }
        
        return shares.stream()
                .map(MomentShareDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userSharesPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentShareDTO> getSharesByUserId(Long userId, Pageable pageable) {
        log.debug("Getting paginated shares by user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<MomentShare> sharesPage = momentShareRepository.findByUserOrderByCreatedAtDesc(user, pageable);
        
        Page<MomentShareDTO> dtoPage = sharesPage.map(MomentShareDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "shareCount", key = "'moment:' + #originalMomentId")
    public Long countSharesByOriginalMomentId(Long originalMomentId) {
        log.debug("Counting shares for original moment ID: {}", originalMomentId);
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        return momentShareRepository.countByOriginalMoment(originalMoment);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "shareCount", key = "'user:' + #userId")
    public Long countSharesByUserId(Long userId) {
        log.debug("Counting shares by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        return momentShareRepository.countByUser(user);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentSharers", key = "#originalMomentId")
    public List<UserDTO> getUsersWhoSharedMoment(Long originalMomentId) {
        log.debug("Getting all users who shared original moment ID: {}", originalMomentId);
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        List<User> users = momentShareRepository.findUsersWhoSharedMoment(originalMoment);
        
        if (users.isEmpty()) {
            return Collections.emptyList();
        }
        
        return users.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentSharersPaged", 
              key = "#originalMomentId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<UserDTO> getUsersWhoSharedMoment(Long originalMomentId, Pageable pageable) {
        log.debug("Getting paginated users who shared original moment ID: {} with page: {}", 
                originalMomentId, pageable.getPageNumber());
        
        Moment originalMoment = momentRepository.findById(originalMomentId)
                .orElseThrow(() -> new MomentNotFoundException(originalMomentId));
        
        Page<User> usersPage = momentShareRepository.findUsersWhoSharedMoment(originalMoment, pageable);
        
        Page<UserDTO> dtoPage = usersPage.map(UserDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsWithMostShares", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentWithSharesDTO> getMomentsWithMostSharesVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting moments with most shares visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Object[]> results = momentShareRepository.findMomentsWithMostSharesVisibleToUser(viewer, pageable);
        
        if (results.isEmpty()) {
            return Collections.emptyList();
        }
        
        return results.stream()
                .map(result -> {
                    Moment moment = (Moment) result[0];
                    Long shareCount = (Long) result[1];
                    
                    // Get some recent shares for additional info
                    List<MomentShare> recentShares = momentShareRepository
                            .findByOriginalMomentOrderByCreatedAtDesc(moment, Pageable.ofSize(3))
                            .getContent();
                    
                    List<MomentShareDTO> recentShareDTOs = recentShares.stream()
                            .map(MomentShareDTO::fromEntity)
                            .collect(Collectors.toList());
                    
                    return MomentWithSharesDTO.fromMomentAndShares(moment, shareCount, recentShareDTOs, viewerId);
                })
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "recentlySharedByFriends", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public List<MomentDTO> getRecentlySharedMomentsByFriends(Long userId, Pageable pageable) {
        log.debug("Getting recently shared moments by friends of user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = momentShareRepository.findRecentlySharedMomentsByFriends(user, pageable);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithShareStatus(moment, userId))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsByShareComment", 
              key = "#searchTerm + '-' + #viewerId")
    public List<MomentDTO> findMomentsByShareComment(String searchTerm, Long viewerId) {
        log.debug("Finding moments by share comment: {} for viewer ID: {}", searchTerm, viewerId);
        
        if (searchTerm == null || searchTerm.trim().isEmpty()) {
            return Collections.emptyList();
        }
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Moment> moments = momentShareRepository.findOriginalMomentsByShareComment(searchTerm, viewer);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithShareStatus(moment, viewerId))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "batchShareStatus", key = "#userId + '-' + #momentIds.hashCode()")
    public Map<Long, Boolean> batchCheckUserSharedMoments(Long userId, List<Long> momentIds) {
        log.debug("Batch checking if user ID: {} has shared moments: {}", userId, momentIds);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Map<Long, Boolean> result = new HashMap<>();
        
        // Initialize all as not shared
        momentIds.forEach(momentId -> result.put(momentId, false));
        
        // Get all moments from the repository to avoid N+1 queries
        List<Moment> moments = momentRepository.findAllById(momentIds);
        
        // Find all shares for this user and the given moments
        for (Moment moment : moments) {
            boolean shared = momentShareRepository.existsByUserAndOriginalMoment(user, moment);
            result.put(moment.getId(), shared);
        }
        
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "batchShareCounts", key = "#momentIds.hashCode()")
    public Map<Long, Long> batchGetShareCountsForMoments(List<Long> momentIds) {
        log.debug("Batch getting share counts for moments: {}", momentIds);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        Map<Long, Long> result = new HashMap<>();
        
        // Get all moments from the repository to avoid N+1 queries
        List<Moment> moments = momentRepository.findAllById(momentIds);
        
        // Count shares for each moment
        for (Moment moment : moments) {
            Long count = momentShareRepository.countByOriginalMoment(moment);
            result.put(moment.getId(), count);
        }
        
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "shareStats", key = "#userId")
    public ShareStatsDTO getShareStatsByUser(Long userId) {
        log.debug("Getting share statistics for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        // Get all shares by the user
        List<MomentShare> userShares = momentShareRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (userShares.isEmpty()) {
            return ShareStatsDTO.builder()
                    .userId(userId)
                    .username(user.getUsername())
                    .totalShareCount(0L)
                    .momentsSharedCount(0L)
                    .timesBeingSharedCount(0L)
                    .avgSharesPerMoment(0.0)
                    .lastShareDate(null)
                    .mostSharedMoments(Collections.emptyList())
                    .topShareComments(Collections.emptyList())
                    .mostActiveTimeOfDay("N/A")
                    .build();
        }
        
        // Count unique moments shared by the user
        Set<Long> uniqueMomentsShared = userShares.stream()
                .map(share -> share.getOriginalMoment().getId())
                .collect(Collectors.toSet());
        long momentsSharedCount = uniqueMomentsShared.size();
        
        // Count times the user's moments were shared by others
        // In a real implementation, this would be a custom query
        long timesBeingSharedCount = 0; // Placeholder
        
        // Calculate average shares per moment
        double avgSharesPerMoment = momentsSharedCount > 0 
                ? (double) userShares.size() / momentsSharedCount
                : 0;
        
        // Get last share date
        LocalDateTime lastShareDate = userShares.isEmpty() ? null : userShares.get(0).getCreatedAt();
        
        // Find most shared moments
        // In a real implementation, this would be a custom query
        List<MomentDTO> mostSharedMoments = Collections.emptyList(); // Placeholder
        
        // Find top share comments
        List<String> topShareComments = userShares.stream()
                .filter(share -> share.getAdditionalComment() != null && !share.getAdditionalComment().isEmpty())
                .map(MomentShare::getAdditionalComment)
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(5)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        
        // Find most active time of day for sharing
        String mostActiveTimeOfDay = findMostActiveTimeOfDay(userShares);
        
        return ShareStatsDTO.builder()
                .userId(userId)
                .username(user.getUsername())
                .totalShareCount((long) userShares.size())
                .momentsSharedCount(momentsSharedCount)
                .timesBeingSharedCount(timesBeingSharedCount)
                .avgSharesPerMoment(avgSharesPerMoment)
                .lastShareDate(lastShareDate)
                .mostSharedMoments(mostSharedMoments)
                .topShareComments(topShareComments)
                .mostActiveTimeOfDay(mostActiveTimeOfDay)
                .build();
    }
    
    /**
     * Analyzes share times to find most active time of day
     */
    private String findMostActiveTimeOfDay(List<MomentShare> shares) {
        Map<String, Long> timeOfDayCounts = new HashMap<>();
        timeOfDayCounts.put("Morning (6-12)", 0L);
        timeOfDayCounts.put("Afternoon (12-18)", 0L);
        timeOfDayCounts.put("Evening (18-24)", 0L);
        timeOfDayCounts.put("Night (0-6)", 0L);
        
        for (MomentShare share : shares) {
            LocalDateTime dateTime = share.getCreatedAt();
            if (dateTime == null) continue;
            
            LocalTime time = dateTime.toLocalTime();
            int hour = time.getHour();
            
            if (hour >= 6 && hour < 12) {
                timeOfDayCounts.put("Morning (6-12)", timeOfDayCounts.get("Morning (6-12)") + 1);
            } else if (hour >= 12 && hour < 18) {
                timeOfDayCounts.put("Afternoon (12-18)", timeOfDayCounts.get("Afternoon (12-18)") + 1);
            } else if (hour >= 18) {
                timeOfDayCounts.put("Evening (18-24)", timeOfDayCounts.get("Evening (18-24)") + 1);
            } else {
                timeOfDayCounts.put("Night (0-6)", timeOfDayCounts.get("Night (0-6)") + 1);
            }
        }
        
        return timeOfDayCounts.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElse("N/A");
    }
} 